package pay.payClient;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.common.utils.FileUtils;
import com.common.utils.ObjectUtils;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryRequest;
import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;

import lombok.SneakyThrows;
import pay.config.WXPayClientConfig;
import com.common.enums.PayOrderStatusRespEnum ;
import pay.module.notify.PayNotifyReqDTO;
import pay.module.notify.PayOrderNotifyRespDTO;
import pay.module.order.PayOrderRespDTO;
import pay.module.refund.PayRefundUnifiedReqDTO;
import pay.module.refund.PayRefundUnifiedRespDTO;


import java.time.LocalDateTime;
import java.util.Objects;

import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN;
import static pay.config.WXPayClientConfig.API_VERSION_V2;


/**
 * @author 王伟羽
 * @date 2023/9/15 15:42
 */

public abstract class AbstractWeXinClient extends AbstractPayClient<WXPayClientConfig> {

    protected WxPayService client;

    public AbstractWeXinClient(String channelId, String channelCode, WXPayClientConfig config) {
        super(channelId, channelCode, config);
    }
    /**
     * 初始化 payClient 客户端
     *
     * @param tradeType 交易类型
     */
    protected void doInit(String tradeType) {
        // 创建 config 配置
        WxPayConfig payConfig = new WxPayConfig();
        //忽略读取两个文件中的内容，因为后续可能会有乱码的影响
        BeanUtil.copyProperties(config, payConfig, "keyContent","privateKeyContent","privateCertContent");
        payConfig.setTradeType(tradeType);
        // weixin-pay-java 无法设置内容，只允许读取文件，所以这里要创建临时文件来解决
//        if (Base64.isBase64(config.getKeyContent())) {
//            payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath());
//        }
        if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
            payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
        }
        if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
            payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
        }

        // 创建 payClient 客户端
        client = new WxPayServiceImpl();
        client.setConfig(payConfig);
    }



    @Override
    @SneakyThrows
    public Object parseNotify(PayNotifyReqDTO rawNotify) {

        // 1. 解析回调
        WxPayOrderNotifyV3Result response = client.parseOrderNotifyV3Result(rawNotify.getBody(), null);
        WxPayOrderNotifyV3Result.DecryptNotifyResult result = response.getResult();
// 2. 构建结果
        Integer status = parseStatus(result.getTradeState());
        String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null;
        PayOrderNotifyRespDTO respDTO = new PayOrderNotifyRespDTO();
        respDTO.setTradeStatus(result.getTradeState());
        respDTO.setChannelOrderNo(result.getOutTradeNo());
        respDTO.setChannelUserId(openid);
        respDTO.setSuccessTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"));
        respDTO.setOrderExtensionNo(result.getOutTradeNo());
        return respDTO;
    }

    private static Integer parseStatus(String tradeState) {
        switch (tradeState) {
            case "NOTPAY":
            case "USERPAYING": // 支付中，等待用户输入密码（条码支付独有）
                return PayOrderStatusRespEnum.WAITING.getStatus();
            case "SUCCESS":
                return PayOrderStatusRespEnum.SUCCESS.getStatus();
            case "REFUND":
                return PayOrderStatusRespEnum.REFUND.getStatus();
            case "CLOSED":
            case "REVOKED": // 已撤销（刷卡支付独有）
            case "PAYERROR": // 支付失败（其它原因，如银行返回失败）
                return PayOrderStatusRespEnum.CLOSED.getStatus();
            default:
                throw new IllegalArgumentException(StrUtil.format("未知的支付状态({})", tradeState));
        }
    }

    @Override
    protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable {
        try {
            switch (config.getApiVersion()) {
                case API_VERSION_V2:
                    return doGetOrderV2(outTradeNo);
                case WXPayClientConfig.API_VERSION_V3:
                    return doGetOrderV3(outTradeNo);
                default:
                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
            }
        } catch (WxPayException e) {
            if (ObjectUtils.equalsAny(e.getErrCode(), "ORDERNOTEXIST", "ORDER_NOT_EXIST")) {
                String errorCode = getErrorCode(e);
                String errorMessage = getErrorMessage(e);
                return PayOrderRespDTO.closedOf(errorCode, errorMessage,
                        outTradeNo, e.getXmlString());
            }
            throw e;
        }
    }

    private PayOrderRespDTO doGetOrderV2(String outTradeNo) throws WxPayException {
        // 构建 WxPayUnifiedOrderRequest 对象
        WxPayOrderQueryRequest request = WxPayOrderQueryRequest.newBuilder()
                .outTradeNo(outTradeNo).build();
        // 执行请求
        WxPayOrderQueryResult response = client.queryOrder(request);

        // 转换结果
        Integer status = parseStatus(response.getTradeState());
        return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
                outTradeNo, response);
    }

    private PayOrderRespDTO doGetOrderV3(String outTradeNo) throws WxPayException {
        // 构建 WxPayUnifiedOrderRequest 对象
        WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request()
                .setOutTradeNo(outTradeNo);
        // 执行请求
        WxPayOrderQueryV3Result response = client.queryOrderV3(request);

        // 转换结果
        Integer status = parseStatus(response.getTradeState());
        String openid = response.getPayer() != null ? response.getPayer().getOpenid() : null;
        return PayOrderRespDTO.of(status, response.getTransactionId(), openid, parseDateV3(response.getSuccessTime()),
                outTradeNo, response);
    }

    static String getErrorCode(WxPayException e) {
        if (StrUtil.isNotEmpty(e.getErrCode())) {
            return e.getErrCode();
        }
        if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) {
            return "CUSTOM_ERROR";
        }
        return e.getReturnCode();
    }

    static String getErrorMessage(WxPayException e) {
        if (StrUtil.isNotEmpty(e.getErrCode())) {
            return e.getErrCodeDes();
        }
        if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) {
            return e.getCustomErrorMsg();
        }
        return e.getReturnMsg();
    }

    static LocalDateTime parseDateV2(String time) {
        return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN);
    }


    static LocalDateTime parseDateV3(String time) {
        return LocalDateTimeUtil.parse(time, "yyyy-MM-dd'T'HH:mm:ssXXX");
    }


    @Override
    protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
        try {
            switch (config.getApiVersion()) {
                case API_VERSION_V2:
                    return doUnifiedRefundV2(reqDTO);
                case WXPayClientConfig.API_VERSION_V3:
                    return doUnifiedRefundV3(reqDTO);
                default:
                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
            }
        } catch (WxPayException e) {
            String errorCode = getErrorCode(e);
            String errorMessage = getErrorMessage(e);
            return PayRefundUnifiedRespDTO.failureOf(errorCode, errorMessage,
                    reqDTO.getPayTradeNo(), e.getXmlString());
        }
    }

    private PayRefundUnifiedRespDTO doUnifiedRefundV2(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
        // 1. 构建 WxPayRefundRequest 请求
        WxPayRefundRequest request = new WxPayRefundRequest()
                .setOutTradeNo(reqDTO.getPayTradeNo())
                .setOutRefundNo(reqDTO.getPayTradeNo())
                .setRefundFee(reqDTO.getAmount().intValue())
                .setRefundDesc(reqDTO.getReason())
                .setTotalFee(reqDTO.getOriginAmount())
                .setNotifyUrl(reqDTO.getNotifyUrl());

        // 2.1 执行请求
        WxPayRefundResult response = client.refundV2(request);
        // 2.2 创建返回结果
        if (Objects.equals("SUCCESS", response.getResultCode())) { // V2 情况下，不直接返回退款成功，而是等待异步通知
            return PayRefundUnifiedRespDTO.waitingOf(response.getRefundId(),
                    reqDTO.getPayTradeNo(), response);
        }
        return PayRefundUnifiedRespDTO.failureOf(reqDTO.getPayTradeNo(), response);
    }

    private PayRefundUnifiedRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
        // 1. 构建 WxPayRefundRequest 请求
        WxPayRefundV3Request request = new WxPayRefundV3Request()
                .setOutTradeNo(reqDTO.getPayTradeNo())
                .setOutRefundNo(reqDTO.getPayTradeNo())
                .setAmount(new WxPayRefundV3Request.Amount().setRefund(reqDTO.getAmount().intValue())
                        .setTotal(reqDTO.getOriginAmount()).setCurrency("CNY"))
                .setReason(reqDTO.getReason())
                .setNotifyUrl(reqDTO.getNotifyUrl());

        // 2.1 执行请求
        WxPayRefundV3Result response = client.refundV3(request);
        // 2.2 创建返回结果
        if (Objects.equals("SUCCESS", response.getStatus())) {
            return PayRefundUnifiedRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()),
                    reqDTO.getPayTradeNo(), response);
        }
        if (Objects.equals("PROCESSING", response.getStatus())) {
            return PayRefundUnifiedRespDTO.waitingOf(response.getRefundId(),
                    reqDTO.getPayTradeNo(), response);
        }
        return PayRefundUnifiedRespDTO.failureOf(reqDTO.getPayTradeNo(), response);
    }
}
